package ua.edu.nuos.jeetraining2012.cms.servlet;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.json.simple.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ua.edu.nuos.jeetraining2012.cms.dto.predefined.StandardUserStatuses;
import ua.edu.nuos.jeetraining2012.cms.entity.StudentEntity;
import ua.edu.nuos.jeetraining2012.cms.entity.UserEntity;
import ua.edu.nuos.jeetraining2012.cms.service.Student;
import ua.edu.nuos.jeetraining2012.cms.service.User;
import ua.edu.nuos.jeetraining2012.cms.service.exception.ServiceException;
import ua.edu.nuos.jeetraining2012.cms.service.exception.ServiceValidationException;
import ua.edu.nuos.jeetraining2012.cms.util.LocaleWrapper;
import ua.edu.nuos.jeetraining2012.cms.util.interceptor.ShiroSecurity;
import ua.edu.nuos.jeetraining2012.cms.util.mail.Mail;
import ua.edu.nuos.jeetraining2012.cms.util.mail.user.Signup;
import ua.edu.nuos.jeetraining2012.cms.util.servlet.CmsServlet;
import ua.edu.nuos.jeetraining2012.cms.util.template.layout.DefaultLayoutTemplate;
import ua.edu.nuos.jeetraining2012.cms.util.validation.EmailAddressValidator;

import javax.ejb.EJB;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;


/**
 * Servlet for performing after login,logout redirection to VaadinUI.
 */
@ShiroSecurity
@WebServlet(urlPatterns = {"/user", "/user/*"})
public class UserServlet extends CmsServlet {
    @EJB
    protected User user;
    @EJB
    protected Student student;
    @EJB
    protected Mail mail;

    final static private Logger logger = LoggerFactory.getLogger(UserServlet.class);

    /**
     * Entry point after authentication via Apache Shiro Library (VK provider).
     * Place user entity object into a session if already exist and redirect to context path
     */
    public void loginAction(HttpServletRequest request, HttpServletResponse response) throws IOException {
        Subject subject = SecurityUtils.getSubject();
        UserEntity dto = new UserEntity();
        dto.setId(subject.getPrincipals().getPrimaryPrincipal().toString());
        logger.debug("loginAction() - User {} authenticated", dto.getId());
        try {
            dto = user.findById(dto);
            if (dto != null) {
                subject.getSession().setAttribute("user", dto);
            }
        } catch (ServiceValidationException e) {
            Set<ConstraintViolation<UserEntity>> validationResult = (Set<ConstraintViolation<UserEntity>>) e.getValidationResult();
            for (ConstraintViolation<UserEntity> v : validationResult)
                logger.debug("loginAction() user.findByUserId() ServiceValidationException exception message {} , Invalid value {}", v.getMessage(), v.getInvalidValue());
        } catch (ServiceException e) {
            logger.error("loginAction() user.findByUserId() ServiceException ", e);
        }
        response.sendRedirect(request.getContextPath());
    }

    /**
     * Entry after authentication via Apache Shiro Library (Google Provider).
     * Place user entity object into a session if already exist and redirect to context path
     */
    public void logingoogleAction(HttpServletRequest request, HttpServletResponse response) throws IOException {
        Subject subject = SecurityUtils.getSubject();
        UserEntity dto = new UserEntity();
        dto.setId(subject.getPrincipals().getPrimaryPrincipal().toString());
        logger.debug("logingoogleAction() - User {} authenticated", dto.getId());
        try {
            dto = user.findById(dto);
            if (dto != null) {
                subject.getSession().setAttribute("user", dto);
            } else {
                dto = user.loadDTOShiro();
                user.setRegistrationValues(dto);
                dto.setStatus(StandardUserStatuses.ACTIVE);
                user.persist(dto);
                subject.getSession().setAttribute("user", dto);
            }
        } catch (ServiceValidationException e) {
            Set<ConstraintViolation<UserEntity>> validationResult = (Set<ConstraintViolation<UserEntity>>) e.getValidationResult();
            for (ConstraintViolation<UserEntity> v : validationResult)
                logger.debug("loginAction() user.findByUserId() ServiceValidationException exception message {} , Invalid value {}", v.getMessage(), v.getInvalidValue());
        } catch (ServiceException e) {
            logger.error("loginAction() user.findByUserId() ServiceException ", e);
        }
        response.sendRedirect(request.getContextPath());
    }

    /**
     * Perform logout
     */
    public void logoutAction(HttpServletRequest request, HttpServletResponse response) throws IOException {
        Subject subject = SecurityUtils.getSubject();

        String userId = (String) subject.getPrincipals().getPrimaryPrincipal();
        subject.logout();
        logger.debug("logoutAction() - User {} logged out", userId);
        response.sendRedirect(request.getContextPath());
    }

    /**
     * Sign up user in a database and send registration email
     */
    public void signupAction(HttpServletRequest request, HttpServletResponse response) throws IOException {
        Subject subject = SecurityUtils.getSubject();
        UserEntity dto = new UserEntity();
        dto.setId(subject.getPrincipals().getPrimaryPrincipal().toString());
        String email = request.getParameter("email");
        EmailAddressValidator validator = new EmailAddressValidator();
        try {
            String errMsg = null;
            ResourceBundle bundle = LocaleWrapper.getBundle("emailsignupform");
            if (user.isExist(dto)) {
                logger.debug("signupAction() User already exist in a storage. User id {}", dto.getId());
                errMsg = bundle.getString("useralreadyexist");
            } else if (!validator.isValid(email, null)) {
                logger.debug("signupAction() email value is not valid. Supplied value {}", email);
                errMsg = bundle.getString("emailinvalid");
            }
            JSONObject obj = new JSONObject();
            if (errMsg != null) {
                obj.put("error", errMsg);
                response.setStatus(404);
                response.getWriter().write(obj.toString());
                return;
            }
            dto = user.loadDTOShiro();
            dto.setEmail(email);
            user.setRegistrationValues(dto);
            user.persist(dto);
            subject.getSession().setAttribute("user", dto);
            Signup mailSignup = new Signup();
            mailSignup.setUser(dto);
            mailSignup.setApproveURL(getRequestURL(request) + "/user/approve");
            mail.send(mailSignup);
            response.getWriter().write(obj.toString());
        } catch (ServiceValidationException e) {
            Set<ConstraintViolation<UserEntity>> validationResult = (Set<ConstraintViolation<UserEntity>>) e.getValidationResult();
            for (ConstraintViolation<UserEntity> v : validationResult)
                logger.debug("signupAction() ServiceValidationException exception message {} , Invalid value {}", v.getMessage(), v.getInvalidValue());
        } catch (ServiceException e) {
            logger.error("signupAction() ServiceException Message", e);
        }
    }

    /**
     * Set user locale. Locale must be set via locale parameter.
     * Return 404 HTTP response code if locale is not supported
     */
    public void localeAction(HttpServletRequest request, HttpServletResponse response) throws IOException {
        Subject subject = SecurityUtils.getSubject();
        String name = request.getParameter("locale");
        if ((name != null) && (name.equals("ru_RU") || name.equals("uk_UA"))) {
            LocaleWrapper.setLocale(name.substring(0, 2), name.substring(3));
        } else {
            response.setStatus(404);
        }
    }

    /**
     * Computes user hash and compares against provided by user.
     * If hash are matched user is placed in active status
     *
     * @param request  request
     * @param response response
     * @throws IOException
     */
    public void approveAction(HttpServletRequest request, HttpServletResponse response) throws IOException {
        Subject subject = SecurityUtils.getSubject();
        String email = request.getParameter("email");
        String md5 = request.getParameter("id");

        ResourceBundle bundle = user.getResourceBundle();
        try {
            subject.getSession().setAttribute(DefaultLayoutTemplate.FLASH_MESSAGE, user.activate(email, md5));
        } catch (ServiceValidationException | ServiceException | IllegalArgumentException e) {
            subject.getSession().setAttribute(DefaultLayoutTemplate.FLASH_MESSAGE, bundle.getString("activateGeneralError"));
            logger.error("User activate ServiceException", e);
        }
        response.sendRedirect(request.getContextPath());
    }


    /**
     * Method read current profile data (tables: users and students) from db.
     * Send Profile data, as JSON Object to client.
     * If the Method can't get Profile data, it send error, as JSON Object to client, about this.
     * @param request
     * @param response
     * @throws IOException
     */
    public void getprofileAction(HttpServletRequest request, HttpServletResponse response) throws IOException {
        logger.debug("Method getprofileAction() - invocation");

        Subject subject = SecurityUtils.getSubject();
        UserEntity dto = new UserEntity();
        dto.setId(subject.getPrincipals().getPrimaryPrincipal().toString());

        JSONObject obj;

        try {
            Map<String, Object> map  = user.getUserProfile(dto);
            obj = new JSONObject(map);
        } catch (ServiceException e) {
            obj = new JSONObject();
            obj.put("errors", e.getMessage());
        }

        response.getWriter().write(obj.toString());
    }


    /**
     * Method save filled or edited, by client, user profile.
     * If the Method can't valid, filled or edited by client, Profile data,
     * it send errors, as JSON Object to client, about this to enter correct data.
     * @param request
     * @param response
     * @throws IOException
     */
    public void saveprofileAction(HttpServletRequest request, HttpServletResponse response) throws IOException {
        logger.debug("Meethod saveprofileAction() - start");
        ResourceBundle bundle = LocaleWrapper.getBundle("profileForm");

        JSONObject obj = new JSONObject();
        Subject subject = SecurityUtils.getSubject();
        UserEntity dto = (UserEntity) subject.getSession().getAttribute("user");

        StudentEntity studentEntity = getChangeStudEnt(request, dto, obj);
        dto = getChangeUserEntity(request, dto);

        try {
            user.persist(dto);
            student.persist(studentEntity);
            subject.getSession().setAttribute("user", dto);
            obj.put("good", bundle.getString("wasSaved"));
            response.getWriter().write(obj.toString());
        } catch (ServiceValidationException e) {
            logger.warn("ServiceValidationException  was send JSON");
            Set<ConstraintViolation<StudentEntity>> validationResult = e.getValidationResult();
            for (ConstraintViolation<StudentEntity> valid : validationResult) {
                obj.put(valid.getPropertyPath().toString(), bundle.getString(valid.getMessage()));
            }
            sendError(obj, response);
        } catch (ServiceException e) {
            logger.warn("ServiceException  - " + e.getMessage());
            obj.put("errors", e.getMessage());
            sendError(obj, response);
        }
    }


    /**
     * Method set status as Error for client response and
     * send JSON Object with profile Data Errors to client
     * @param error JSON Collection with errors
     * @param response response
     * @throws IOException
     */
    private void sendError(JSONObject error, HttpServletResponse response) throws IOException {
        response.setStatus(404);
        response.getWriter().write(error.toString());
    }


    /**
     * Method get changed data about user, by profile form, from POST request,  and set absent RegistrationValues
     * @param request request
     * @param dto empty UserEntity with id
     * @return  filled UserEntity
     */
    private UserEntity getChangeUserEntity(HttpServletRequest request, UserEntity dto) {

        String name = checkForNull(request.getParameter("name"));
        String email = checkForNull(request.getParameter("email"));

        dto.setName(name);
        dto.setEmail(email);

        if (!(dto.getSvnPassword()!=null && !dto.getSvnPassword().isEmpty())){
            dto.setSvnPassword(user.createUserPassword());
        }

        return dto;
    }

    /**
     * Method get filled or changed data about student , by profile form,
     * from POST request, and Create full StudentEntity.
     * If request has incorrect SimpleDateFormat, JSONObj put error, about this.
     * @param request request
     * @param dto empty StudentEntity
     * @param obj JSONObject
     * @return filled StudentEntity
     */
    private StudentEntity getChangeStudEnt(HttpServletRequest request, UserEntity dto, JSONObject obj) {

        Date date = null;
        ResourceBundle bundle = LocaleWrapper.getBundle("profileForm");
        StudentEntity studentEntity = new StudentEntity();
        String birthday = checkForNull(request.getParameter("birthday"));
        String education = checkForNull(request.getParameter("education"));
        String occupation = checkForNull(request.getParameter("occupation"));
        String photo = checkForNull(request.getParameter("photo"));
        String phone = checkForNull(request.getParameter("phone"));
        String skype = checkForNull(request.getParameter("skype"));


        if (birthday!=null) {
            try {
                date = new SimpleDateFormat("dd.MM.yyyy").parse(birthday);
                logger.warn("Date Afer parse" + date);
            } catch (ParseException e) {
                logger.warn("Date ParseException - " + e.getMessage());
                obj.put("birthday", bundle.getString(e.getMessage()));
            }
        }

        studentEntity.setBirthday(date);
        studentEntity.setEducation(education);
        studentEntity.setOccupation(occupation);
        studentEntity.setPhoto(photo);
        studentEntity.setUserId(dto);
        studentEntity.setPhone(phone);
        studentEntity.setSkype(skype);


        return studentEntity;
    }

    /**
     * Method check is parameter empty ?
     * @param parameter
     * @return true if is empty or false otherwise
     */
    private String checkForNull(String  parameter){
        return (parameter.equals(""))?null:removeHTML(parameter);
    }

    /**
     * Method removed HTML tag and HTML code from string by String replaceAll().
     * After removing HTML from string it will return string in text.
     * @param   htmlString
     *          String that consist html tag or code
     * @return  return string in text.
     */
    private String removeHTML(String htmlString)
    {
        String noHTMLString = htmlString.replaceAll("\\<.*?\\>", "");

        noHTMLString = noHTMLString.replaceAll("\r", "<br/>");

        noHTMLString = noHTMLString.replaceAll("\n", " ");
        noHTMLString = noHTMLString.replaceAll("\'", "&#39;");
        noHTMLString = noHTMLString.replaceAll("\"", "&quot;");
        return noHTMLString;
    }

}
